package com.shixj.ef.execute.base;

import com.shixj.ef.config.FetchConfig;
import com.shixj.ef.constant.HttpCodeCons;
import com.shixj.ef.constant.RegexCons;
import com.shixj.ef.constant.StringCons;
import com.shixj.ef.execute.HttpFetchExecute;
import com.shixj.ef.vo.FetchRequest;
import com.shixj.ef.vo.FetchResponse;
import com.shixj.ef.vo.FetchResult;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;

/**
 * Created by shixj on 2017/9/19.
 */
public class BaseHttpFetchHandler implements Runnable {
    private static final Logger LOGGER = LoggerFactory.getLogger(BaseHttpFetchHandler.class);
    private HttpFetchExecute executor;

    public BaseHttpFetchHandler(HttpFetchExecute executor) {
        this.executor = executor;
    }


    @Override
    public void run() {
        while (true) {
            if (this.executor.getConfig().getMax() == -1 || this.executor.getFetchResults().size() < this.executor.getConfig().getMax()) {
                FetchResponse response;
                if (this.executor.getConfig().isAsync())
                    response = this.executor.takeResponse();
                else response = this.executor.pollResponse(this.executor.getConfig().getDelayTime());
                if (response == null) break;
                else handle(response);
            } else break;
        }
        this.executor.setRun(false);
    }


    public void handle(FetchResponse response) {
        boolean goDeeper = goDeeper(response);//是否继续爬取外链
        CloseableHttpResponse httpResponse = response.getHttpResponse();
        FetchRequest request = response.getFetchRequest();
        String fetchUrl = request.getFetchUrl();
        int httpCode = httpResponse.getStatusLine().getStatusCode();
        if (httpCode >= HttpCodeCons.ERROR_BAD_REQUEST) {
            //do nothing
        } else if (httpCode >= HttpCodeCons.SUCCESS_GET && httpCode <= HttpCodeCons.SUCCESS_LIMIT) {
            try {
                byte[] htmlArr = EntityUtils.toByteArray(httpResponse.getEntity());
                String html = new String(htmlArr);
                Matcher ma = RegexCons.PATTERN_CHARSET.matcher(html);
                while (ma.find()) {
                    String meta = ma.group();
                    String charset = meta.substring(8, meta.endsWith(" ") || meta.endsWith("\"") ? meta.length() - 1 : meta.length());
                    if (charset.matches(RegexCons.CHARSET_VALUE))
                        html = new String(htmlArr, charset);
                }
                //解析
                FetchResult fetchResult = FetchResult.build(request, parseHtmlToDoc(html));
                this.executor.addFetchResult(fetchResult);
                this.executor.putResult(fetchResult);
                LOGGER.info(fetchUrl + "解析成功");
                if (goDeeper) {
                    int depth = request.getDepth();
                    //分析外链
                    List<String> outerLinks = getOuterLink(html, fetchUrl, depth + 1);

                    for (String link : outerLinks) {
                        createFetchRequest(link, request);
                    }

                }
            } catch (IOException e) {
                e.printStackTrace();
            }


        } else if (httpCode == HttpCodeCons.SUCCESS_REDIRECT) {
            //重定向
            request.setIsRedirected(true);
            createFetchRequest(httpResponse.getFirstHeader("location").getValue(), request);

        }


    }

    private Document parseHtmlToDoc(String html) {
        Document doc;
        try {
            doc = Jsoup.parse(html);
            if (FetchConfig.documentHandler != null)
                FetchConfig.documentHandler.handelDocument(doc);
        } catch (Exception e) {
            doc = null;
        }
        return doc;

    }

    public List<String> getOuterLink(String html, String parentUrl, int depth) {
        Matcher maDomain = RegexCons.PATTERN_DOMAIN.matcher(parentUrl);
        String domain = "";
        while (maDomain.find()) {
            domain = maDomain.group();
        }
        final List<String> list = new ArrayList<>();
        Matcher ma = RegexCons.PATTERN_ALINK.matcher(html);
        while (ma.find()) {
            String link = ma.group();
            Matcher maHref = RegexCons.PATTERN_ALINK_HREF.matcher(link);
            while (maHref.find()) {
                String href = maHref.group();
                String uri = href.substring(6, href.length() - 1);
                String suffix = uri.replaceAll(RegexCons.DOMAIN, StringCons.EMPTY);
                if (!suffix.equals(StringCons.EMPTY) && uri.matches(RegexCons.DOMAIN))
                    addUrl(uri, list, depth);
                else {
                    String url;
                    if (!uri.startsWith("/")) {
                        if (parentUrl.endsWith("/"))
                            url = parentUrl + uri;
                        else url = parentUrl + "/" + uri;
                    } else
                        url = domain + uri;
                    addUrl(url, list, depth);
                }
            }
        }
        return list;
    }

    private void addUrl(String url, List<String> list, int depth) {
        if (url.matches(RegexCons.FULL_URL) && !list.contains(url)) {
            boolean useable = true;
            Map<Integer, String> depthRegexMap = this.executor.getConfig().getDepthRegexMap();
            String depthRegex = depthRegexMap.get(new Integer(depth));
            String regex = this.executor.getConfig().getRegex();
            if (depthRegex != null) {
                if (!url.matches(depthRegex))
                    useable = false;
            } else if (regex != null && !url.matches(regex))
                useable = false;
            if (useable)
                list.add(url);
        }
    }

    private boolean goDeeper(FetchResponse response) {
        int max = this.executor.getConfig().getMax();
        if (max == -1 || this.executor.getFetchResults().size() <= max) {
            int limitDepth = this.executor.getConfig().getDepth();
            int depth = response.getFetchRequest().getDepth();
            if (limitDepth == -1)
                return true;
            if (limitDepth <= depth)
                return false;
            else return true;
        } else return false;
    }

    private void createFetchRequest(String link, FetchRequest oldRequest) {
        FetchRequest newRequest = new FetchRequest();
        newRequest.setHttpClient(oldRequest.getHttpClient());
        newRequest.setIsRedirected(oldRequest.getIsRedirected());
        newRequest.setOriginalUrl(oldRequest.getFetchUrl());
        newRequest.setFetchUrl(link);
        if (!oldRequest.getIsRedirected())
            newRequest.setDepth(oldRequest.getDepth() + 1);
        this.executor.putRequest(newRequest);
    }
}
